﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace Super_Pong.Classes
{
    public enum EMITTER_TYPE { BOX, LINEX, LINEY, POINT, CIRCLE };
    public enum SPRAY_TYPE { RADIAL, DIR_UP, DIR_DOWN, DIR_LEFT, DIR_RIGHT, DIR_ANGLE };

    class ParticleEmitter : GameObject
    {
        //TODO: add a FACE DIRECTION to the particles and to this emitter

        #region Atributes
        //The particles holder
        List<Particle> particles;
        private int MAX_PARTICLES;

        //PARTICLE PROPRIETIES
        //Life
        private long defaultLifeTime;
        private float randLifeTime;
        //Size
        private float endSize;
        private float defaultSize;
        private float randSize;
        //Animation
        private Animation particleAnim;
        //Physics
        private float gravity;
        //Rotation
        private float defaultRotSpeed;
        private float randRotSpeed;
        //Color
        private Color startColor;
        private Color endColor;
        //Velocity
        private float defaultSpeed;
        private float randSpeed;
        //Auto randomize starting angle
        private bool randStartAngle;
        //Face direction
        private bool particlesFaceDirection;

        //EMITER PROPRIETIES
        //Global random generator
        Random rnd = new Random(DateTime.Now.Millisecond);
        //Position
        private Vector2 position;
        //Size
        private float size;
        //Directional angle
        private float angle;
        //Type
        private EMITTER_TYPE type = EMITTER_TYPE.BOX;
        private SPRAY_TYPE stype = SPRAY_TYPE.RADIAL;
        //A list to mark the particles to remove
        private List<Particle> removeParticles;
        //A vector to apply gravity
        private Vector2 DOWN = new Vector2(0, 1);
        private Vector2 UP = new Vector2(0, -1);
        private Vector2 LEFT = new Vector2(-1, 0);
        private Vector2 RIGHT = new Vector2(1, 0);
        //The blend to render the particle
        private BlendState blendMode;
        #endregion

        #region Constructors
        /// <summary>
        ///  A default constructor for test pourpouses.
        /// </summary>
        /// <param name="pAnim">An anim to see some particles.</param>
        /// <param name="pos">The emiter position.</param>
        public ParticleEmitter(Animation pAnim, Vector2 pos)
        {
            particles = new List<Particle>();
            removeParticles = new List<Particle>();

            //Particles defaults
            defaultLifeTime = 1000L;
            randLifeTime = 0.4f;

            endSize = 0.0f;
            defaultSize = 0.7f;
            randSize = 0.1f;
        
            particleAnim = pAnim;
        
            gravity = 0f;
        
            defaultRotSpeed = 0.05f;
            randRotSpeed = 0.2f;
        
            startColor = Color.Yellow;
            endColor = Color.Red;

            defaultSpeed = 0.1f;
            randSpeed = 0.2f;

            angle = 45;
            randStartAngle = true;

            //Emitter defaults
            position = pos;
            size = 50;
            type = EMITTER_TYPE.BOX;
            stype = SPRAY_TYPE.RADIAL;
            MAX_PARTICLES = 8000;
        }

        /// <summary>
        /// A complete constructor to build a particle emitter.
        /// </summary>
        /// <param name="pAnim">The animation for every particle.</param>
        /// <param name="pos">The emiter position.</param>
        /// <param name="sz">The emiter size.</param>
        /// <param name="ang">The angle to spray in DIR_ANGLE mode.</param>
        /// <param name="RSA">Automatically randomize particles starting angles?.</param>
        /// <param name="FDIR">Particles face the direction they are heading?.</param>
        /// <param name="tp">The emiter type.</param>
        /// <param name="stp">The spray type.</param>
        /// <param name="maxP">The maximum particles to spawn.</param>
        /// <param name="dfLifeTime">The particles default lifeTime.</param>
        /// <param name="rndLifeTime">The percent of variation for the default param. 0 = no variation, 1 = varies the full default value.</param>
        /// <param name="eSize">Determine the particle end size.</param>
        /// <param name="dfSize">The particles default size.</param>
        /// <param name="rndSize">The percent of variation for the default param. 0 = no variation, 1 = varies the full default value.</param>
        /// <param name="dfSpeed">The default speed to spray the particles.</param>
        /// <param name="rndSpeed">The percent of variation for the default param. 0 = no variation, 1 = varies the full default value.</param>
        /// <param name="grav">The gravity to apply to the particles.</param>
        /// <param name="dfRot">The default rotation speed.</param>
        /// <param name="rndRot">The percent of variation for the default param. 0 = no variation, 1 = varies the full default value</param>
        /// <param name="startCol">The particles starting color.</param>
        /// <param name="endCol">The particles ending color.</param>
        /// <param name="bMode">The particles blend mode.</param>
        public ParticleEmitter(Animation pAnim, Vector2 pos, float sz, float ang, bool RSA, bool FDIR, EMITTER_TYPE tp, SPRAY_TYPE stp, int maxP, 
            int dfLifeTime, float rndLifeTime, float eSize, float dfSize, float rndSize, float dfSpeed, float rndSpeed, float grav,
            float dfRot, float rndRot, Color startCol, Color endCol, BlendState bMode)
        {
            //Receive emitter proprieties
            position = pos;
            size = sz;
            type = tp;
            stype = stp;
            angle = ang;
            randStartAngle = RSA;
            MAX_PARTICLES = maxP;
            blendMode = bMode;
            particlesFaceDirection = FDIR;

            //Receive particles proprieties
            //Anim
            particleAnim = pAnim;
            //Life
            defaultLifeTime = dfLifeTime;
            randLifeTime = rndLifeTime;
            //Size
            endSize = eSize;
            defaultSize = dfSize;
            randLifeTime = rndSize;
            //Rotation
            defaultRotSpeed = dfRot;
            randRotSpeed = rndRot;
            //Color
            startColor = startCol;
            endColor = endCol;
            //Physics
            gravity = grav;
            defaultSpeed = dfSpeed;
            randSpeed = rndSpeed;

            //The holder
            particles = new List<Particle>();
            removeParticles = new List<Particle>();
        }

        /// <summary>
        /// A complete constructor to build a particle emitter.
        /// </summary>
        /// <param name="pAnim">The animation for every particle.</param>
        /// <param name="pos">The emiter position.</param>
        /// <param name="sz">The emiter size.</param>
        /// <param name="RSA">Automatically randomize particles starting angles?.</param>
        /// <param name="FDIR">Particles face the direction they are heading?.</param>
        /// <param name="tp">The emiter type.</param>
        /// <param name="stp">The spray type.</param>
        /// <param name="maxP">The maximum particles to spawn.</param>
        /// <param name="dfLifeTime">The particles default lifeTime.</param>
        /// <param name="rndLifeTime">The percent of variation for the default param. 0 = no variation, 1 = varies the full default value.</param>
        /// <param name="eSize">Determine the particle end size.</param>
        /// <param name="dfSize">The particles default size.</param>
        /// <param name="rndSize">The percent of variation for the default param. 0 = no variation, 1 = varies the full default value.</param>
        /// <param name="dfSpeed">The default speed to spray the particles.</param>
        /// <param name="rndSpeed">The percent of variation for the default param. 0 = no variation, 1 = varies the full default value.</param>
        /// <param name="grav">The gravity to apply to the particles.</param>
        /// <param name="dfRot">The default rotation speed.</param>
        /// <param name="rndRot">The percent of variation for the default param. 0 = no variation, 1 = varies the full default value</param>
        /// <param name="startCol">The particles starting color.</param>
        /// <param name="endCol">The particles ending color.</param>
        /// <param name="bMode">The particles blend mode.</param>
        public ParticleEmitter(Animation pAnim, Vector2 pos, float sz, bool RSA, bool FDIR, EMITTER_TYPE tp, SPRAY_TYPE stp, int maxP,
            int dfLifeTime, float rndLifeTime, float eSize, float dfSize, float rndSize, float dfSpeed, float rndSpeed, float grav,
            float dfRot, float rndRot, Color startCol, Color endCol, BlendState bMode)
        {
            //Receive emitter proprieties
            position = pos;
            size = sz;
            type = tp;
            stype = stp;
            angle = 0;
            randStartAngle = RSA;
            particlesFaceDirection = FDIR;
            MAX_PARTICLES = maxP;
            blendMode = bMode;

            //Receive particles proprieties
            //Anim
            particleAnim = pAnim;
            //Life
            defaultLifeTime = dfLifeTime;
            randLifeTime = rndLifeTime;
            //Size
            endSize = eSize;
            defaultSize = dfSize;
            randLifeTime = rndSize;
            //Rotation
            defaultRotSpeed = dfRot;
            randRotSpeed = rndRot;
            //Color
            startColor = startCol;
            endColor = endCol;
            //Physics
            gravity = grav;
            defaultSpeed = dfSpeed;
            randSpeed = rndSpeed;

            //The holder
            particles = new List<Particle>();
            removeParticles = new List<Particle>();
        }

        /// <summary>
        /// A complete constructor to build a particle emitter.
        /// </summary>
        /// <param name="pAnim">The animation for every particle.</param>
        /// <param name="pos">The emiter position.</param>
        /// <param name="sz">The emiter size.</param>
        /// <param name="tp">The emiter type.</param>
        /// <param name="stp">The spray type.</param>
        /// <param name="maxP">The maximum particles to spawn.</param>
        /// <param name="dfLifeTime">The particles default lifeTime.</param>
        /// <param name="rndLifeTime">The percent of variation for the default param. 0 = no variation, 1 = varies the full default value.</param>
        /// <param name="eSize">Determine the particle end size.</param>
        /// <param name="dfSize">The particles default size.</param>
        /// <param name="rndSize">The percent of variation for the default param. 0 = no variation, 1 = varies the full default value.</param>
        /// <param name="dfSpeed">The default speed to spray the particles.</param>
        /// <param name="rndSpeed">The percent of variation for the default param. 0 = no variation, 1 = varies the full default value.</param>
        /// <param name="grav">The gravity to apply to the particles.</param>
        /// <param name="dfRot">The default rotation speed.</param>
        /// <param name="rndRot">The percent of variation for the default param. 0 = no variation, 1 = varies the full default value</param>
        /// <param name="bMode">The particles blend mode.</param>
        public ParticleEmitter(Animation pAnim, Vector2 pos, float sz, EMITTER_TYPE tp, SPRAY_TYPE stp, int maxP,
            int dfLifeTime, float rndLifeTime, float eSize, float dfSize, float rndSize, float dfSpeed, float rndSpeed, float grav,
            float dfRot, float rndRot, BlendState bMode)
        {
            //Receive emitter proprieties
            position = pos;
            size = sz;
            type = tp;
            stype = stp;
            randStartAngle = false;
            MAX_PARTICLES = maxP;
            blendMode = bMode;

            //Receive particles proprieties
            //Anim
            particleAnim = pAnim;
            //Life
            defaultLifeTime = dfLifeTime;
            randLifeTime = rndLifeTime;
            //Size
            endSize = eSize;
            defaultSize = dfSize;
            randLifeTime = rndSize;
            //Rotation
            defaultRotSpeed = dfRot;
            randRotSpeed = rndRot;
            //Color
            startColor = Color.White;
            endColor = Color.White;
            //Physics
            gravity = grav;
            defaultSpeed = dfSpeed;
            randSpeed = rndSpeed;

            //The holder
            particles = new List<Particle>();
            removeParticles = new List<Particle>();
        }
        #endregion

        #region Methods

        /// <summary>
        /// Update the emitter.
        /// </summary>
        /// <param name="time">The game time.</param>
        public override void Update(GameTime time)
        {
            //UPDATE EACH PARTICLE
            //If there are living particles update them, else remove them from the list
            if(particles.Count() > 0){

                //Scan the particle to update or remove them
                foreach(Particle p in particles){
                    //If is alive update
                    if (p.isAlive())
                    {
                        //Update it dude!
                        p.Update(time);

                        //APPLY FORCES AND STUFF
                        if (gravity != 0.0f)
                        {
                            p.accelerate(DOWN,gravity);
                        }

                    }
                    else
                    {
                        //KILL IT!
                        removeParticles.Add(p);
                    }
                }

                //If there are any particle in the remove list remove them from the particles list
                //Fica uma dica para o compilador de executar essa parte ouvindo Kill them all do metallica
                // \m/ (^.^) \m/
                foreach(Particle r in removeParticles){
                    if(particles.Contains(r)){
                        particles.Remove(r);
                    }
                }
                //Clear the remove list
                removeParticles.Clear();
            }

        }

        /// <summary>
        /// Draw all the living particles of this emitter.
        /// </summary>
        /// <param name="sb">The batch to draw the stuff.</param>
        public override void Draw(SpriteBatch sb)
        {
            if (particles.Count() > 0)
            {
                foreach(Particle p in particles)
                {
                    p.Draw(sb);
                }
            }
        }

        /// <summary>
        /// Emit a particle using the emiter parameters.
        /// </summary>
        public void emitParticle()
        {
            if(particles.Count() < MAX_PARTICLES){
                //Generate the parameters
                //Position BASED ON EMITTER TYPE
                Vector2 particlePos = generateParticlePosByEmitterType();
                //Life
                long particleLife = (long)randomizeParameter(defaultLifeTime,randLifeTime);
                //Size
                float particleStartSize = randomizeParameter(defaultSize,randSize);
                //Rotation
                float particleRot = expandedRandomizeParameter(defaultRotSpeed,randRotSpeed);
                //Grab the default parameters and apply to the new particle
                Particle p = new Particle(particleAnim, particlePos, particleLife, particleRot, particlesFaceDirection, particleStartSize, endSize, startColor, endColor, blendMode);
                //Apply a direction and a speed to the new generated particle
                applySprayToParticle(p);
                //Add the particle to the list
                particles.Add(p);
            }
        }

        /// <summary>
        /// Emit particles using the emiter parameters.
        /// </summary>
        public void emitParticle(int amount)
        {
            for (int i = 0; i < amount; i++)
            {
                emitParticle();
            }
        }

        #region Private helpers
        /// <summary>
        /// Randomize the value with a percent of it own size.
        /// </summary>
        /// <param name="value">The value to get a variation.</param>
        /// <param name="percent">The variation amount in percent (0-1).</param>
        /// <returns></returns>
        private float randomizeParameter(float value, float percent)
        {
            //Lock the percent in the correct range
            percent = MathHelper.Clamp(percent, 0, 1);
            //Create a variable to return
            float v;
            //Moved the random generator to optmize memory
            //Randomize a number from -1 to 1 (range 2)
            float random = ((float)rnd.NextDouble())*2.0f - 1.0f;
            //The final value becomes the value +or- a random percentage of it based on percent
            v = value + ((value * percent) * random);
            //Return it
            return v;
        }

        /// <summary>
        /// Randomize the value with a percent of it own size. Goes to negative.
        /// </summary>
        /// <param name="value">The value to get a variation.</param>
        /// <param name="percent">The variation amount in percent (0-1).</param>
        /// <returns></returns>
        private float expandedRandomizeParameter(float value, float percent)
        {
            //Lock the percent in the correct range
            percent = MathHelper.Clamp(percent, 0, 1);
            //Create a variable to return
            float v;
            //Randomize a number from -1 to 1 (range 2)
            float random = ((float)rnd.NextDouble()) * 2.0f - 1.0f;
            //The final value becomes the value +or- a random percentage of it based on percent
            v = value + ((value * percent) * random);
            //Generate a new random
            random = rnd.Next(2) * 2.0f - 1.0f;
            random = (random < 0 ? -1 : 1);
            v *= (int)random;
            //Return it
            return v;
        }

        /// <summary>
        /// Randomize from 0 to value. (by yamanaka)
        /// </summary>
        /// <param name="value">The max.</param>
        /// <returns>A random number between 0 and value.</returns>
        private float randomizeValue(float value)
        {
            //Create our random generator
            Random rnd = new Random();
            //Return it
            return (float)rnd.Next((int)-value, (int)value);
        }


        /// <summary>
        /// Based on the emitter type and size, this method generate a particle position within range.
        /// </summary>
        /// <returns>The generated Vector2. Apply to the particle.</returns>
        private Vector2 generateParticlePosByEmitterType()
        {
            Vector2 particlePos = new Vector2(0, 0);
            switch (type)
            {
                case EMITTER_TYPE.BOX:
                    //Spawn the particle in a square shape
                    particlePos.X = this.position.X + randomizeParameter(size,1.0f) - size;
                    particlePos.Y = this.position.Y + randomizeValue(size);
                    break;
                case EMITTER_TYPE.CIRCLE:
                    //Spawn in a circle shape
                    particlePos.X = this.position.X + randomizeValue(size) * (float)Math.Sin((randomizeParameter(360, 1.0f) / 2.0f));
                    particlePos.Y = this.position.Y + randomizeValue(size) * (float)Math.Cos((randomizeParameter(360, 1.0f) / 2.0f));
                    break;
                case EMITTER_TYPE.LINEX:
                    //Spawn the particle in a line shape
                    particlePos.X = this.position.X + randomizeValue(size);
                    //Thin layer
                    particlePos.Y = this.position.Y + randomizeParameter(0.5f, 1.0f) - 1.0f;
                    break;
                case EMITTER_TYPE.LINEY:
                    //Spawn the particle in a line shape
                    //Thin layer
                    particlePos.X = this.position.X + randomizeParameter(0.5f, 1.0f) - 1.0f;
                    particlePos.Y = this.position.Y + randomizeValue(size);
                    break;
                case EMITTER_TYPE.POINT:
                    particlePos.X = this.position.X + randomizeParameter(0.5f, 1.0f) - 1.0f;
                    particlePos.Y = this.position.Y + randomizeParameter(0.5f, 1.0f) - 1.0f;
                    break;
            }

            return particlePos;
        }

        /// <summary>
        /// Add a spray direction and velocity to a particle.
        /// </summary>
        /// <param name="p">The particle to receive the physics.</param>
        private void applySprayToParticle(Particle p)
        {
            if(p != null){
                //Create Direction
                Vector2 direction;
                //Select a speed to apply to this particle
                float speed = randomizeParameter(defaultSpeed,randSpeed);
                //Select and apply a direction to this particle
                switch(stype){
                    case SPRAY_TYPE.DIR_UP:
                        p.setDirection(UP);
                        break;
                    case SPRAY_TYPE.DIR_DOWN:
                        p.setDirection(DOWN);
                        break;
                    case SPRAY_TYPE.DIR_LEFT:
                        p.setDirection(LEFT);
                        break;
                    case SPRAY_TYPE.DIR_RIGHT:
                        p.setDirection(RIGHT);
                        break;
                    case SPRAY_TYPE.RADIAL:
                        direction = p.getDirectionTo(position);
                        p.setDirection(-direction);
                        break;
                    case SPRAY_TYPE.DIR_ANGLE:
                        direction = new Vector2((float)Math.Cos(angle), -(float)Math.Sin(angle));
                        p.setDirection(direction);
                        break;
                }
                //Apply the RSA
                if (randStartAngle)
                {
                    p.setAngle(randomizeValue(360));
                }

                //Apply the speed
                p.setSpeed(speed);
            }
        }
        #endregion

        #endregion

        #region Gets and sets
        /// <summary>
        /// Set the emitter position.
        /// </summary>
        /// <param name="pos">The position to assign.</param>
        public void setPos(Vector2 pos)
        {
            this.position = pos;
        }

        /// <summary>
        /// Set the emitter position.
        /// </summary>
        /// <param name="x">The X position to assign.</param>
        /// <param name="y">The Y position to assign.</param>
        public void setPos(float x, float y)
        {
            this.position.X = x;
            this.position.Y = y;
        }

        /// <summary>
        /// Return the list containing all the particles.
        /// </summary>
        /// <returns>list containing all the particles</returns>
        public List<Particle> getParticles()
        {
            return particles;
        }
        #endregion

    }
}
